#pragma once

#include "logger.h"
#include "obj_pointer.h"
#include "buffer.h"
#include <string.h>

OPEN_JLIB_NS


template<bool _BigEndian = true>
struct ByteBuffer : public Obj {

  inline ByteBuffer () noexcept {}

  inline explicit ByteBuffer (uwl capacity) noexcept : _buffer(capacity), _r_index(0), _w_index(0) {
    debug_assert(capacity);
  }

  inline ByteBuffer (void* data, uwl capacity) noexcept : _buffer(data, capacity), _r_index(0), _w_index(0) {
  }

  inline ByteBuffer (void* data, uwl capacity, uwl payload) noexcept : _buffer(data, capacity), _r_index(0), _w_index(payload) {
    debug_assert(capacity >= payload);
  }

  inline ByteBuffer (const ByteBuffer& other) noexcept : _buffer(other._buffer), _r_index(other._r_index), _w_index(other._w_index) {
  }

  inline ByteBuffer (ByteBuffer&& other) noexcept : _buffer(std::move(other._buffer)), _r_index(other._r_index), _w_index(other._w_index) {
  }


  inline static ByteBuffer create (void* data, uwl size) noexcept {
    auto base = malloc(size);
    memcpy(base, data, size);
    return {base, size, size};
  }


  inline ByteBuffer& operator= (const ByteBuffer& other) noexcept {
    _buffer = other._buffer;
    _r_index = other._r_index;
    _w_index = other._w_index;
    return *this;
  }

  inline ByteBuffer& operator= (ByteBuffer&& other) noexcept {
    _buffer = std::move(other._buffer);
    _r_index = other._r_index;
    _w_index = other._w_index;
    return *this;
  }



  inline bool available () const noexcept {
    return _buffer;
  }

  inline uwl readable () const noexcept {
    debug_assert(_buffer);
    return _w_index - _r_index;
  }

  inline uwl writable () const noexcept {
    debug_assert(_buffer);
    return _buffer->size() - _w_index;
  }

  inline uwl size () const noexcept {
    debug_assert(_buffer);
    return _buffer->size();
  }

  inline u8* base () noexcept {
    debug_assert(_buffer);
    return _buffer->data();
  }

  inline const u8* base () const noexcept {
    debug_assert(_buffer);
    return _buffer->data();
  }

  inline const u8* readIter () const noexcept {
    debug_assert(_buffer);
    return _buffer->data() + _r_index;
  }

  inline u8* writeIter () noexcept {
    debug_assert(_buffer);
    return _buffer->data() + _w_index;
  }


  inline uwl readIndex () const noexcept {
    debug_assert(_buffer);
    return _r_index;
  }

  inline uwl writeIndex () const noexcept {
    debug_assert(_buffer);
    return _w_index;
  }

  inline void readIndex (uwl index) noexcept {
    debug_assert(_buffer && index <= _w_index);
    _r_index = index;
  }

  inline void writeIndex (uwl index) noexcept {
    debug_assert(_buffer && index <= _buffer->size());
    _w_index = index;
  }

  inline void skip (swl offset) noexcept {
    debug_assert(_buffer && _r_index >= offset && _r_index + offset <= _w_index);
    _r_index += offset;
  }

  inline void move (swl offset) noexcept {
    debug_assert(_buffer && _w_index >= offset && _w_index + offset <= _buffer->size());
    _w_index += offset;
  }


  inline void normalize () noexcept {
    debug_assert(_buffer);
    if (!_r_index)
      return;
    auto size = readable();
    memmove(base(), readIter(), size);
    _r_index = 0;
    _w_index = size;
  }

  inline void prepare (uwl size) noexcept {
    debug_assert(_buffer);
    if (writable() < size)
      _buffer->resize((_w_index + size) << 1);
  }


  inline u8 readByte () noexcept {
    debug_assert(_buffer && _r_index < _w_index);
    return _buffer->read(_r_index++);
  }

  inline void writeByte (u8 byte) noexcept {
    prepare(sizeof(u8));
    _buffer->write(_w_index++, byte);
  }


  template<typename _Type = u16, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 2 && _BigEndian != (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline _Type readWord () noexcept {
    debug_assert(_buffer && _r_index < _w_index - 1);
    u16 b0 = _buffer->read(_r_index++);
    u16 b1 = _buffer->read(_r_index++);
    u16 res = b0 << 8;
    res |= b1;
    return (_Type)res;
  }

  template<typename _Type = u16, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 2 && _BigEndian == (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline _Type readWord () noexcept {
    debug_assert(_buffer && _r_index < _w_index - 1);
    u16 b1 = _buffer->read(_r_index++);
    u16 b0 = _buffer->read(_r_index++);
    u16 res = b0 << 8;
    res |= b1;
    return (_Type)res;
  }

  template<typename _Type = u16, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 2 && _BigEndian != (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline void writeWord (_Type value) noexcept {
    prepare(sizeof(u16));
    auto word = (u16)value;
    auto b0 = (u8)(word >> 8);
    auto b1 = (u8)(word);
    _buffer->write(_w_index++, b0);
    _buffer->write(_w_index++, b1);
  }

  template<typename _Type = u16, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 2 && _BigEndian == (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline void writeWord (_Type value) noexcept {
    prepare(sizeof(u16));
    auto word = (u16)value;
    auto b1 = (u8)(word >> 8);
    auto b0 = (u8)(word);
    _buffer->write(_w_index++, b0);
    _buffer->write(_w_index++, b1);
  }


  template<typename _Type = u32, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 4 && _BigEndian != (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline _Type readDWord () noexcept {
    debug_assert(_buffer && _r_index < _w_index - 3);
    u32 b0 = _buffer->read(_r_index++);
    u32 b1 = _buffer->read(_r_index++);
    u32 b2 = _buffer->read(_r_index++);
    u32 b3 = _buffer->read(_r_index++);
    u32 res = b0 << 24;
    res |= b1 << 16;
    res |= b2 << 8;
    res |= b3;
    return (_Type)res;
  }

  template<typename _Type = u32, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 4 && _BigEndian == (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline _Type readDWord () noexcept {
    debug_assert(_buffer && _r_index < _w_index - 3);
    u32 b3 = _buffer->read(_r_index++);
    u32 b2 = _buffer->read(_r_index++);
    u32 b1 = _buffer->read(_r_index++);
    u32 b0 = _buffer->read(_r_index++);
    u32 res = b0 << 24;
    res |= b1 << 16;
    res |= b2 << 8;
    res |= b3;
    return (_Type)res;
  }

  template<typename _Type = u32, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 4 && _BigEndian != (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline void writeDWord (_Type value) noexcept {
    prepare(sizeof(u32));
    auto dword = (u32)value;
    auto b0 = (u8)(dword >> 24);
    auto b1 = (u8)(dword >> 16);
    auto b2 = (u8)(dword >> 8);
    auto b3 = (u8)(dword);
    _buffer->write(_w_index++, b0);
    _buffer->write(_w_index++, b1);
    _buffer->write(_w_index++, b2);
    _buffer->write(_w_index++, b3);
  }

  template<typename _Type = u32, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 4 && _BigEndian == (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline void writeDWord (_Type value) noexcept {
    prepare(sizeof(u32));
    auto dword = (u32)value;
    auto b3 = (u8)(dword >> 24);
    auto b2 = (u8)(dword >> 16);
    auto b1 = (u8)(dword >> 8);
    auto b0 = (u8)(dword);
    _buffer->write(_w_index++, b0);
    _buffer->write(_w_index++, b1);
    _buffer->write(_w_index++, b2);
    _buffer->write(_w_index++, b3);
  }


  template<typename _Type = u64, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 8 && _BigEndian != (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline _Type readQWord () noexcept {
    debug_assert(_buffer && _r_index < _w_index - 7);
    u64 b0 = _buffer->read(_r_index++);
    u64 b1 = _buffer->read(_r_index++);
    u64 b2 = _buffer->read(_r_index++);
    u64 b3 = _buffer->read(_r_index++);
    u64 b4 = _buffer->read(_r_index++);
    u64 b5 = _buffer->read(_r_index++);
    u64 b6 = _buffer->read(_r_index++);
    u64 b7 = _buffer->read(_r_index++);
    u64 res = b0 << 56;
    res |= b1 << 48;
    res |= b2 << 40;
    res |= b3 << 32;
    res |= b4 << 24;
    res |= b5 << 16;
    res |= b6 << 8;
    res |= b7;
    return (_Type)res;
  }

  template<typename _Type = u64, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 8 && _BigEndian == (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline _Type readQWord () noexcept {
    debug_assert(_buffer && _r_index < _w_index - 7);
    u64 b7 = _buffer->read(_r_index++);
    u64 b6 = _buffer->read(_r_index++);
    u64 b5 = _buffer->read(_r_index++);
    u64 b4 = _buffer->read(_r_index++);
    u64 b3 = _buffer->read(_r_index++);
    u64 b2 = _buffer->read(_r_index++);
    u64 b1 = _buffer->read(_r_index++);
    u64 b0 = _buffer->read(_r_index++);
    u64 res = b0 << 56;
    res |= b1 << 48;
    res |= b2 << 40;
    res |= b3 << 32;
    res |= b4 << 24;
    res |= b5 << 16;
    res |= b6 << 8;
    res |= b7;
    return (_Type)res;
  }

  template<typename _Type = u64, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 8 && _BigEndian != (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline void writeQWord (_Type value) noexcept {
    prepare(sizeof(u64));
    auto qword = (u64)value;
    auto b0 = (u8)(qword >> 56);
    auto b1 = (u8)(qword >> 48);
    auto b2 = (u8)(qword >> 40);
    auto b3 = (u8)(qword >> 32);
    auto b4 = (u8)(qword >> 24);
    auto b5 = (u8)(qword >> 16);
    auto b6 = (u8)(qword >> 8);
    auto b7 = (u8)(qword);
    _buffer->write(_w_index++, b0);
    _buffer->write(_w_index++, b1);
    _buffer->write(_w_index++, b2);
    _buffer->write(_w_index++, b3);
    _buffer->write(_w_index++, b4);
    _buffer->write(_w_index++, b5);
    _buffer->write(_w_index++, b6);
    _buffer->write(_w_index++, b7);
  }

  template<typename _Type = u64, std::enable_if_t<std::is_integral<_Type>::value && sizeof(_Type) == 8 && _BigEndian == (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__), int> = 0>
  inline void writeQWord (_Type value) noexcept {
    prepare(sizeof(u64));
    auto qword = (u64)value;
    auto b7 = (u8)(qword >> 56);
    auto b6 = (u8)(qword >> 48);
    auto b5 = (u8)(qword >> 40);
    auto b4 = (u8)(qword >> 32);
    auto b3 = (u8)(qword >> 24);
    auto b2 = (u8)(qword >> 16);
    auto b1 = (u8)(qword >> 8);
    auto b0 = (u8)(qword);
    _buffer->write(_w_index++, b0);
    _buffer->write(_w_index++, b1);
    _buffer->write(_w_index++, b2);
    _buffer->write(_w_index++, b3);
    _buffer->write(_w_index++, b4);
    _buffer->write(_w_index++, b5);
    _buffer->write(_w_index++, b6);
    _buffer->write(_w_index++, b7);
  }


  uwl read (void* data, uwl size) noexcept {
    auto rsize = readable();
    auto rread = rsize < size ? rsize : size;
    memcpy(data, _buffer->data() + _r_index, rread);
    _r_index += rread;
    return rread;
  }

  void write (const void* data, uwl size) noexcept {
    prepare(size);
    memcpy(_buffer->data() + _w_index, data, size);
    _w_index += size;
  }

  void write (ByteBuffer& other) noexcept {
    auto size = other.readable();
    prepare(size);
    memcpy(writeIter(), other.readIter(), size);
    _w_index += size;
    other._r_index += size;
  }


private:
  Ptr<Buffer> _buffer;
  uwl _r_index;
  uwl _w_index;
};


CLOSE_JLIB_NS
